#include "ctrl.h"

#define TAG "[HXD019]"

#define LOGD(fmt, arg...) pr_debug(TAG "[%s %d]" fmt, __func__, __LINE__, ##arg)
#define LOGI(fmt, arg...) pr_info(TAG "[%s %d]" fmt, __func__, __LINE__, ##arg)
#define LOGW(fmt, arg...) pr_warn(TAG "[%s %d]" fmt, __func__, __LINE__, ##arg)
#define LOGE(fmt, arg...) pr_err(TAG "[%s %d]" fmt, __func__, __LINE__, ##arg)

unsigned char learn_cmd[] ={0x30, 0x20, 0x50};

struct hxd019_priv {
	struct device *dev;
	int scl_gpio;
	int sda_gpio;
	int busy_gpio;
	int irq;
	char   val_in[MAX_BUFFER_COUNT];
	char   val_out[MAX_BUFFER_COUNT];
	int in_cnt;
	wait_queue_head_t r_wait;
	struct work_struct work;
	struct delayed_work delayed_work;
};

static struct hxd019_priv *g_hxd019;
static int isOne;

static irqreturn_t hxd019_isr(int irq, void *dev_id)
{
	struct hxd019_priv *priv = dev_id; 
	LOGI("+++");
	priv->in_cnt = 1;
//	do_read(priv->in_val, MAX_BUFFER_COUNT);
	wake_up_interruptible(&priv->r_wait);	
	schedule_work(&priv->work);
	return IRQ_HANDLED;
}

static void hxd019_work(struct work_struct *work)
{
//	struct hxd019_priv *priv = container_of(work, struct hxd019_priv, work);
	LOGI("+++");

}

void delayed_work_func(struct work_struct *work)
{
//	struct hxd019_priv *priv = container_of(delayed_work, struct hxd019_priv, work.work);
	LOGI("+++");	
	do_write(learn_cmd, sizeof(learn_cmd));
}

static int parse_dt(struct device *dev)
{
	struct hxd019_priv * priv = dev_get_drvdata(dev);
	struct device_node *np = dev->of_node;
	int gpio;
	int ret = 0;

	LOGI("+++");
	if(!np || !priv){
		dev_err(dev, "priv or np not found ");
		return -EFAULT;
	}
	gpio = of_get_named_gpio(dev->of_node, "sda-gpio", 0);
	if(gpio == -EPROBE_DEFER){
		return -EPROBE_DEFER;
	}
	if(gpio_is_valid(gpio)){
		priv->sda_gpio = gpio;
		ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH, "sda-gpio");
		if(ret != 0){
			return ret;
		}
	}
	gpio = of_get_named_gpio(dev->of_node, "scl-gpio", 0);
	if(gpio == -EPROBE_DEFER){
		return -EPROBE_DEFER;
	}
	if(gpio_is_valid(gpio)){
		priv->scl_gpio = gpio;
		ret = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH, "scl-gpio");
		if(ret != 0){
			return ret;
		}
	}
	gpio = of_get_named_gpio(dev->of_node, "busy-gpio", 0);
	if(gpio == -EPROBE_DEFER){
		return -EPROBE_DEFER;
	}
	if(gpio_is_valid(gpio)){
		priv->irq = gpio_to_irq(gpio);
		priv->busy_gpio = gpio;
		ret = devm_request_threaded_irq(dev, priv->irq, NULL, hxd019_isr, IRQF_TRIGGER_RISING |IRQF_ONESHOT, "IR", priv);
		if(ret){
			dev_err(dev, "Unable to request IR IRQ, err=%d.\n", ret);
		}
		ret = devm_gpio_request_one(dev, gpio, GPIOF_IN, "busy-gpio");
		if(ret != 0){
			return ret;
		}
	}
	GPIO_I2C_SDA = priv->sda_gpio;
	GPIO_I2C_SCL= priv->scl_gpio;
	GPIO_I2C_BUSY = priv->busy_gpio;
	return 0;
}

// ================= low level operation ====================
void gpio_open(void)
{
	gpio_dat_output();
	gpio_clk_output();
	gpio_clk_hight();
	gpio_dat_hight();
}

void gpio_close(void)
{
	//printk("[Infrared] gpio_close B\n");
	gpio_dat_output();
	gpio_clk_output();
	gpio_clk_hight();
	gpio_dat_hight();
}

void gpio_start(void)
{
	//printk("[Infrared] gpio_start B\n");
	gpio_dat_output();
	gpio_clk_output();
	gpio_clk_hight();
	gpio_dat_hight();

	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_dat_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
}


void gpio_stop(void)
{
	gpio_dat_output();
	gpio_clk_output();
	gpio_clk_low();
	gpio_dat_low();
	udelay(DELAY_TIME_US);
	gpio_clk_hight();
	udelay(DELAY_TIME_US);
	gpio_dat_hight();
	udelay(DELAY_TIME_US);
}


int gpio_check_ack(void)
{

	int ACK_Sign;

	gpio_dat_input();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_hight();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	ACK_Sign = gpio_dat_status();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	return ACK_Sign;
}

void gpio_send_ack(void)
{

	gpio_dat_output();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);

	gpio_clk_hight();
	udelay(DELAY_TIME_US);
	gpio_clk_low();
}

int gpio_read_data(char* data)
{

	char readdata = 0;
	char i = 8;

	gpio_dat_input();
	while (i--){
		readdata <<= 1;
		gpio_clk_hight();
		udelay(DELAY_TIME_US);
		readdata |= gpio_dat_status();
		gpio_clk_low();
		udelay(DELAY_TIME_US);
		udelay(DELAY_TIME_US);
	}
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	*data = readdata;
	gpio_send_ack();

	return 0;
}

int gpio_write_data(char data)
{

	int Data_Bit,ACK_Sign;
	int i;
	//printk("[Infrared] gpio_write_data B\n");

	gpio_dat_output();
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	for(i = 7; i >= 0; i--){
		udelay(DELAY_TIME_US);
		Data_Bit = (data >> i) & 0x01;
		if(Data_Bit){
			gpio_dat_hight();
		}else{
			gpio_dat_low();
		}
		udelay(DELAY_TIME_US);
		gpio_clk_hight();
		udelay(DELAY_TIME_US);
		gpio_clk_low();
	}
	ACK_Sign = gpio_check_ack();
	return ACK_Sign;
}

void gpio_learn(void)
{
	gpio_open();
	udelay(DELAY_TIME_US);
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_hight();
	mdelay(DELAY_TIME_MS);
	gpio_start();
	udelay(DELAY_TIME_US);
	gpio_write_data(0x30);
	udelay(DELAY_TIME_US);
	gpio_write_data(0x10);
	udelay(DELAY_TIME_US);
	gpio_write_data(0x40);
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_stop();
	udelay(DELAY_TIME_US);
	gpio_close();
	udelay(DELAY_TIME_US);
}

//for dat pin
void gpio_dat_input(void)
{
	gpio_direction_input(GPIO_I2C_SDA);
}

void gpio_dat_output(void)
{
	udelay(DELAY_TIME_US);
	gpio_direction_output(GPIO_I2C_SDA,1);
}

void gpio_dat_low(void)
{
	gpio_direction_output(GPIO_I2C_SDA,0);
}

void gpio_dat_hight(void)
{
	gpio_direction_output(GPIO_I2C_SDA,1);
}

int gpio_dat_status(void)
{
	return gpio_get_value(GPIO_I2C_SDA);
}

//for clk pin
void gpio_clk_input(void)
{
	gpio_direction_input(GPIO_I2C_SCL);
}

void gpio_clk_output(void)
{
	gpio_direction_output(GPIO_I2C_SCL,1);
}

void gpio_clk_low(void)
{
	gpio_direction_output(GPIO_I2C_SCL,0);
}

void gpio_clk_hight(void)
{
	gpio_direction_output(GPIO_I2C_SCL,1);
}

//for csn pin
int gpio_clk_status(void)
{
	return gpio_get_value(GPIO_I2C_SCL);
}
void gpio_csn_input(void)
{
	gpio_direction_input(GPIO_I2C_BUSY);
}

void gpio_csn_output(void)
{
	gpio_direction_output(GPIO_I2C_BUSY,0);
}

void gpio_csn_low(void)
{
	gpio_direction_output(GPIO_I2C_BUSY,0);
}

void gpio_csn_hight(void)
{
	gpio_direction_output(GPIO_I2C_BUSY,1);
}

int gpio_csn_status(void)
{
	return __gpio_get_value(GPIO_I2C_BUSY);
}

int do_read(char *out, int _count)
{
	char ch;
	char checksum = 0;
	int i;
	gpio_open();
	udelay(DELAY_TIME_US);
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_hight();
	mdelay(DELAY_TIME_MS);
	gpio_start();
	udelay(DELAY_TIME_US);

	gpio_write_data(0x30);
	udelay(DELAY_TIME_US);
	if (isOne == 0x00){
		gpio_write_data(0x52);
	}else{
		gpio_write_data(0x62);
	}
	udelay(DELAY_TIME_US);

	gpio_start();
	udelay(DELAY_TIME_US);
	gpio_write_data(0x31);
	udelay(DELAY_TIME_US);
	gpio_read_data(&ch);
	if(ch != 0x00){
		gpio_stop();
		udelay(DELAY_TIME_US);
		gpio_close();
		udelay(DELAY_TIME_US);
		LOGE("ch != 0x00\n");
		return -EFAULT;
	}
	*out = ch;
	if (isOne == 0x00){
		checksum = 0xB3;
	}else{
		checksum = 0xC3;
	}

	for(i = 1; i < _count; i++){
		gpio_read_data(&ch);
		udelay(DELAY_TIME_US);
		out[i] = ch;
		checksum += ch;
	}
	gpio_read_data(&ch);
	udelay(DELAY_TIME_US);
	gpio_stop();
	udelay(DELAY_TIME_US);
	gpio_close();
	udelay(DELAY_TIME_US);
	LOGD("dump first ten read data:");
	for(i=0; i< 10; i++){
		printk("%02x ", out[i]);
	}
	printk("\n");

	if(ch != checksum)  {
		LOGW("checksum mismatch: ch=%x, checksum=%x\n", ch, checksum);
		if (isOne == 0){
			return -EFAULT;
		}
	}
	return 0;	
}

int do_write(char *out, int _count)
{
	int i;
	dump_data(out, _count, "ctrl_write");
	if (out[0] == 0x30 && out[1] == 0x10 && out[2] == 0x40){
		isOne = 0;
	}else if (out[0] == 0x30 && out[1] == 0x20 && out[2] == 0x50){
		isOne = 1;
	}

	LOGI("isOne = %d\n", isOne);
	gpio_open();
	udelay(DELAY_TIME_US);
	gpio_clk_low();
	udelay(DELAY_TIME_US);
	udelay(DELAY_TIME_US);
	gpio_clk_hight();

	mdelay(DELAY_TIME_MS);
	gpio_start();
	udelay(DELAY_TIME_US);
	for(i = 0; i < _count; i++){
		gpio_write_data(out[i]);
		udelay(DELAY_TIME_US);
	}
	udelay(DELAY_TIME_US);
	gpio_stop();
	udelay(DELAY_TIME_US);
	gpio_close();
	udelay(DELAY_TIME_US);
	return 0;
}

// ================= low level operation ====================
void dump_data(char *buf, int count, const char *msg)
{
	int i = 0;
	pr_debug("%s:\n{", msg);
	for(i=0; i<count; i++){
		pr_debug("0x%02x ", buf[i]);
	}
	pr_debug("}\n");
}
// =====================  fops related =======================
int hxd019_open(struct inode* inode, struct file* filp)
{
//	struct hxd019_priv* dev = g_hxd019;
	LOGI("+++");
//	if(filp->f_flags & O_RDONLY){
//		LOGI("open for read ");
//		schedule_delayed_work(&dev->delayed_work, 0);   // 3s about
//	}
	enable_irq(g_hxd019->irq);
//	filp->private_data = dev;
	return 0;
}

int hxd019_release(struct inode* inode, struct file* filp)
{
//	struct hxd019_priv* dev = g_hxd019;
	LOGI("+++");
	disable_irq(g_hxd019->irq);
	return 0;
}

ssize_t hxd019_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
{
	size_t _count = 0;
	int ret;
	struct timeval tv1, tv2, tv3, tv4;

	struct hxd019_priv* dev = g_hxd019;
	DECLARE_WAITQUEUE(wait, current);
	do_gettimeofday(&tv1);
	LOGI("count = %lu\n", count);
	if(count <= 0){
		return 0;
	}else if(count > sizeof(dev->val_in) - 1){
		_count = sizeof(dev->val_in) - 1;
	}else {
		_count = count;
	}
	if(*f_pos == 0){
		schedule_delayed_work(&dev->delayed_work, 0); 
	}
	add_wait_queue(&dev->r_wait, &wait);
	while(dev->in_cnt <=0){
		if(filp->f_flags & O_NONBLOCK){
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);
		schedule();
		if(signal_pending(current)){
			ret = -ERESTARTSYS;
			goto out2;
		}
	}
	//memset(dev->val_in, 0xaa, MAX_BUFFER_COUNT-1);
	do_gettimeofday(&tv2);
	pr_info("wait for data ready time taken: %ld millisec",1000 * (tv2.tv_sec - tv1.tv_sec) + (tv2.tv_usec - tv1.tv_usec) / 1000);
	do_read(dev->val_in, _count);
	do_gettimeofday(&tv3);
	pr_info("do_read time taken: %ld millisec",1000 * (tv3.tv_sec - tv2.tv_sec) + (tv3.tv_usec - tv2.tv_usec) / 1000);
	if(copy_to_user(buf, &(dev->val_in), _count)){
		LOGE("copy_to_user fail");
		ret  = -EFAULT;
		goto out;
	}
	do_gettimeofday(&tv4);
	pr_info("copy to user  time taken: %ld millisec",1000 * (tv4.tv_sec - tv3.tv_sec) + (tv4.tv_usec - tv3.tv_usec) / 1000);
	ret = _count;
	*f_pos += 1;  // just as counter
out:
	dev->in_cnt = 0;
out2:
	remove_wait_queue(&dev->r_wait, &wait);
	__set_current_state(TASK_RUNNING);
	schedule_delayed_work(&dev->delayed_work, msecs_to_jiffies(10)); // after 10ms , enter learn mode
	return ret;

}

ssize_t hxd019_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
	size_t _count = 0;
	struct hxd019_priv* dev = g_hxd019;
	struct timeval tv1, tv2;
	do_gettimeofday(&tv1);
	LOGI("count=%lu\n", count);

	if(count > sizeof(dev->val_out) - 1){
		_count = sizeof(dev->val_out) - 1;
	}else{
		_count = count;
	}
	if(copy_from_user(&(dev->val_out), buf, _count)){
		//up(&(dev->sem));
		LOGE("copy_from_user fail\n");
		return -EFAULT;
	}
	do_write(dev->val_out, _count);
	memset(dev->val_out, 0, MAX_BUFFER_COUNT);
	do_gettimeofday(&tv2);
	pr_info("send  time taken: %ld millisec",1000 * (tv2.tv_sec - tv1.tv_sec) + (tv2.tv_usec - tv1.tv_usec) / 1000);
	//up(&(dev->sem));
	return _count;
}

long hxd019_ioctl(/*struct inode* inode, */struct file* filp, unsigned int cmd, unsigned long arg)
{
	LOGI("+++");
	return 0;
}


static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = hxd019_open,
	.release = hxd019_release,
	.unlocked_ioctl = hxd019_ioctl,
	.read = hxd019_read,
	.write = hxd019_write,
};
static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = CTRL_DEVICE_NODE_NAME,
	.fops = &fops,
};

// =====================  fops related =======================
static int hxd019_probe(struct platform_device *pdev)
{
	struct device * dev = &pdev->dev;
	struct hxd019_priv * priv;
	LOGI("+++");
	priv = kzalloc(sizeof(struct hxd019_priv), GFP_KERNEL);
	if(!priv){
		dev_err(dev,"Fail to alloc hxd019 ");
		return -ENOMEM;
	}
	g_hxd019 = priv;
	platform_set_drvdata(pdev, priv);
	priv->dev = dev;
	parse_dt(dev);
	misc_register(&misc);
	INIT_DELAYED_WORK(&priv->delayed_work, delayed_work_func);
	INIT_WORK(&priv->work, hxd019_work);
	init_waitqueue_head(&priv->r_wait);
	return 0;
}

static int hxd019_remove(struct platform_device *pdev)
{
	struct hxd019_priv *priv = platform_get_drvdata(pdev);
	LOGI("+++");
	cancel_delayed_work_sync(&priv->delayed_work);
	cancel_work_sync(&priv->work);
	misc_deregister(&misc);
	if(priv){
		// TODO: 
		kfree(priv);
	}
  	return 0;
}

static const struct of_device_id hxd019_of_match[] = {
  {.compatible = "hxd019", },
  {},
};

static struct platform_driver hxd019_driver = {
  .driver     = {
      .name   = "hxd019",
      .of_match_table = of_match_ptr(hxd019_of_match),
  },
  .probe      = hxd019_probe,
  .remove     = hxd019_remove,
};


module_platform_driver(hxd019_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IR hxd019");
MODULE_AUTHOR("tj<tj_wust@163.com>");